using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using LuaInterface;

namespace LuaNetInterface
{
    /// <summary>
    /// This class provides the access point to the lua scripting engine machine.
    /// Functions and packages can be loaded and run from this virtual machine.
    /// </summary>
    public class LuaVirtualMachine
    {
        #region Data Members
        /// <summary>
        /// The Lua machine that runs the scripts.
        /// </summary>
        private Lua _lua = null;

        /// <summary>
        /// Whether or not the virtual machine is currently running.
        /// </summary>
        private bool _running = true;

        /// <summary>
        /// The registered functions in this virtual machine.
        /// </summary>
        private Hashtable _functions = null;

        /// <summary>
        /// The registered packages in this virtual machine.
        /// </summary>
        private Hashtable _packages = null;
        #endregion

        #region Properties
        /// <summary>
        /// Gets the Lua machine that runs the scripts.
        /// </summary>
        public Lua Lua
        {
            get { return _lua; }
        }

        /// <summary>
        /// Gets whether or not the virtual machine is currently running.
        /// </summary>
        public bool IsRunning
        {
            get { return _running; }
        }

        /// <summary>
        /// Gets the registered functions in this virtual machine.
        /// </summary>
        public Hashtable Functions
        {
            get { return _functions; }
        }

        /// <summary>
        /// Gets the registered packages in this virtual machine.
        /// </summary>
        public Hashtable Packages
        {
            get { return _packages; }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Creates the Lua virtual machine and registers the built-in functions.
        /// </summary>
        public LuaVirtualMachine()
        {
            _lua = new Lua();
            _functions = new Hashtable();
            _packages = new Hashtable();

            // Register the default functions for the virtual machine
            RegisterLuaFunctions(this);
        }

        /// <summary>
        /// Registers the Lua functions from the given object into the Lua virtual machine.
        /// </summary>
        /// <param name="target">The object containing the Lua functions.</param>
        /// <remarks>This function operates by using reflection on the target object.</remarks>
        public void RegisterLuaFunctions(object target)
        {
            // Make sure our virtual machine has been properly initialized
            if (_lua == null || _functions == null || _packages == null)
                return;

            LuaPackageDescriptor packageDescriptor = null;
            Type type = target.GetType();
            object[] attributes = type.GetCustomAttributes(typeof(LuaPackageAttribute), false);
            string package = null;
            string documentation = null;

            // Get any descriptive Lua attributes from the object
            if (attributes != null && attributes.Length > 0)
            {
                package = ((LuaPackageAttribute)attributes[0]).Name;
                documentation = ((LuaPackageAttribute)attributes[0]).Documentation;
            }

            // If our object has been defined as part of a package, register it
            if (package != null)
            {
                _lua.DoString(package + " = {}");
                packageDescriptor = new LuaPackageDescriptor(package, documentation);
            }

            // Go through each of the object's functions and register any marked as Lua functions
            foreach (MethodInfo info in type.GetMethods())
            {
                foreach (Attribute attribute in Attribute.GetCustomAttributes(info))
                {
                    // If this is a Lua attribute, register the function
                    if (attribute.GetType() == typeof(LuaFunctionAttribute))
                    {
                        LuaFunctionAttribute functionAttribute = (LuaFunctionAttribute)attribute;
                        List<string> parameters = new List<string>();
                        List<string> paramDocs = new List<string>();
                        string functionName = functionAttribute.Name;
                        string functionDoc = functionAttribute.Documentation;
                        string[] functionParameters = functionAttribute.Parameters;
                        ParameterInfo[] paramInfo = info.GetParameters();
                        LuaFunctionDescriptor functionDescriptor;

                        // Check the attribute parameters for any errors in definition
                        if ((functionParameters == null && paramInfo.Length > 0) ||
                            (functionParameters != null && (paramInfo.Length != functionParameters.Length)))
                        {
                            if (functionParameters != null)
                            {
                                Console.WriteLine("Function " + info.Name + " (exported as " + functionName +
                                    ") argument number mismatch. Declared " + functionParameters.Length +
                                    " but requires " + paramInfo.Length + ".");
                            }
                            else
                            {
                                Console.WriteLine("Function " + info.Name + " (exported as " + functionName +
                                    ") argument number mismatch. Declared with no arguments" +
                                    " but requires " + paramInfo.Length + ".");
                            }

                            break;
                        }

                        // Add the information to the Lua function
                        for (int i = 0; i < paramInfo.Length; i++)
                        {
                            parameters.Add(paramInfo[i].Name);
                            paramDocs.Add(functionParameters[i]);
                        }
                        
                        functionDescriptor = new LuaFunctionDescriptor(functionName, functionDoc,
                            parameters, paramDocs);

                        // Register the function and assign it to a package if so declared
                        if (packageDescriptor != null)
                        {
                            packageDescriptor.AddFunction(functionDescriptor);
                            _lua.RegisterFunction(package + functionName, target, info);
                            _lua.DoString(package + "." + functionName + " = " + package + functionName);
                            _lua.DoString(package + functionName + " = nil");
                        }
                        else
                        {
                            _functions.Add(functionName, functionDescriptor);
                            _lua.RegisterFunction(functionName, target, info);
                        }
                    }
                }
            }

            // If we're working with a package, register it
            if (packageDescriptor != null)
                _packages.Add(package, packageDescriptor);
        }

        /// <summary>
        /// Stops the Lua virtual machine from running.
        /// </summary>
        public void Stop()
        {
            _running = false;
        }
        #endregion
    }
}
